﻿using S7.Net;
using SimS7Study.Lib;
using System.Collections;
using System.ComponentModel;
using System.Text;
using System.Windows;
using System.Windows.Controls;

namespace SimS7Study
{
    /// <summary>
    /// 第三方库S7netPlus Example
    /// </summary>
    public partial class CaseThrid : Window, INotifyPropertyChanged
    {
        private Plc plc;

        S7Lib s7Lib;
        public CaseThrid()
        {
            InitializeComponent();
            DataContext = this; // 数据上下文

            #region 读Q区
            Task.Run(async () =>
            {
                while (true)
                {
                    // 这里虽然没有绑定到UI线程，但是用到属性设置有时候会出问题，所以要Invoke
                    Dispatcher.Invoke(() =>
                    {
                        ReadQ();
                    });

                    await Task.Delay(1000);
                }
            });
            #endregion

            #region 读DB区
            Task.Run(async () =>
            {
                while (true)
                {
                    // 这里虽然没有绑定到UI线程，但是用到属性设置有时候会出问题，所以要Invoke
                    Dispatcher.Invoke(() =>
                    {
                        ReadDB();
                    });

                    await Task.Delay(1000);
                }
            });
            #endregion



        }

        #region 通知
        public event PropertyChangedEventHandler? PropertyChanged;

        /// <summary>
        /// 通知
        /// </summary>
        /// <param name="propName"></param>
        private void NotifyChanged(string propName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
        }
        #endregion

        #region 绑定属性

        private bool q00;

        public bool Q00
        {
            get { return q00; }
            set
            {
                q00 = value;
                NotifyChanged("Q00"); // 属性发生改变发起通知
            }
        }

        private bool q01;

        public bool Q01
        {
            get { return q01; }
            set
            {
                q01 = value;
                NotifyChanged("Q01"); // 属性发生改变发起通知
            }
        }

        private bool q02;

        public bool Q02
        {
            get { return q02; }
            set
            {
                q02 = value;
                NotifyChanged("Q02"); // 属性发生改变发起通知
            }
        }

        private bool q03;

        public bool Q03
        {
            get { return q03; }
            set
            {
                q03 = value;
                NotifyChanged("Q03");
            }
        }

        private bool q04;

        public bool Q04
        {
            get { return q04; }
            set
            {
                q04 = value;
                NotifyChanged("Q04");
            }
        }

        private bool q05;

        public bool Q05
        {
            get { return q05; }
            set
            {
                q05 = value;
                NotifyChanged("Q05");
            }
        }

        private bool q06;

        public bool Q06
        {
            get { return q06; }
            set
            {
                q06 = value;
                NotifyChanged("Q06");
            }
        }

        private bool q07;

        public bool Q07
        {
            get { return q07; }
            set
            {
                q07 = value;
                NotifyChanged("Q07");
            }
        }



        #endregion

        #region 连接控制
        /// <summary>
        /// 连接
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnConn_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                plc = new Plc(CpuType.S71500, txtIP.Text, 0, 1);
                plc.Open();

                btnConn.Visibility = Visibility.Collapsed;//隐藏连接按钮
                btnDisConn.Visibility = Visibility.Visible;//显示断开按钮
            }
            catch (Exception)
            {
                btnConn.Visibility = Visibility.Visible;//显示连接按钮
                btnDisConn.Visibility = Visibility.Collapsed;//隐藏断开按钮            
            }

        }

        /// <summary>
        /// 断开连接
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnDisConn_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (plc != null && plc.IsConnected)
                {
                    plc.Close();

                    btnConn.Visibility = Visibility.Visible;//显示连接按钮
                    btnDisConn.Visibility = Visibility.Collapsed;//隐藏断开按钮 
                }
            }
            catch (Exception)
            {
                btnConn.Visibility = Visibility.Collapsed;//隐藏连接按钮
                btnDisConn.Visibility = Visibility.Visible;//显示断开按钮     
            }
        }
        #endregion

        #region 读Q区
        
        /// <summary>
        /// 读取字节  读Q0.0~Q0.7
        /// </summary>
        private void ReadQ()
        {
            if (plc != null && plc.IsConnected)
            {
                // 读Q区第0个字节的8位,
                // S7.Net.DataType.Output:代表Q区
                // 第二个参数是DB块(读Q区就设为0)
                // 第三个参数是字节的地址
                // 第四个参数是要读取的类型
                // 第五个参数是要读多少个
                // 第六个参数是你要从哪个位开始读(默认值是0,可以不写)
                Object? result = plc.Read(S7.Net.DataType.Output, 0, 0, VarType.Bit, 8, 0);
                // 读取DB1的第0个字节
                //plc.Read("DB1.DBB0");  // 相当于：//plc.Read(S7.Net.DataType.DataBlock, 1, 0, VarType.Byte, 1);

                // 将BitArray转成bool数组
                if (result is BitArray)
                {
                    BitArray bitArray = (BitArray)result;
                    bool[] boolArray = new bool[bitArray.Length];
                    bitArray.CopyTo(boolArray, 0);

                    Q00 = boolArray[0];
                    Q01 = boolArray[1];
                    Q02 = boolArray[2];
                    Q03 = boolArray[3];
                    Q04 = boolArray[4];
                    Q05 = boolArray[5];
                    Q06 = boolArray[6];
                    Q07 = boolArray[7];
                }
            }
        }

        #endregion

        #region 读DB区


        // PLC的word->ushort PLC的int->short PLC的real->float  PLC的string[10]->string
        public static string ReadPlcString(Plc plc, int dbNumber, int offset)
        {
            // STRING[n] 至少需要 2 字节（最大长度 + 当前长度）
            byte[] header = plc.ReadBytes(S7.Net.DataType.DataBlock, dbNumber, offset, 2);
            if (header == null || header.Length < 2)
                return string.Empty;

            byte maxLen = header[0];
            byte curLen = header[1];

            // 防止越界读取
            int totalLength = 2 + curLen;
            byte[] fullData = plc.ReadBytes(S7.Net.DataType.DataBlock, dbNumber, offset, totalLength);

            if (fullData == null || fullData.Length < totalLength)
                return string.Empty;

            // 提取字符串内容（跳过前两个字节）
            byte[] strBytes = new byte[curLen];
            Array.Copy(fullData, 2, strBytes, 0, curLen);

            // 使用 ANSI 编码（与 PLC 默认一致）
            return Encoding.Default.GetString(strBytes).TrimEnd('\0');
        }

        /// <summary>
        ///  批量读取(连续的字节)
        ///  一次连续读取20个字节 word->ushort(DB1.DBW0)  PLC的int->short(DB1.DBW2)  PLC的real->float(DB1.DBD4)  PLC的string[10]->string(P#DB1.DBX8.0)
        /// </summary>
        private void ReadDB()
        {
            if (plc != null && plc.IsConnected)
            {

                //// 一个一个读
                ////Word DB1.DBW0
                //object? result = plc.Read(S7.Net.DataType.DataBlock, 1, 0, VarType.Word, 1);
                //txtDBW0.Text = result.ToString();
                ////PLC Int-->c# short DB1.DBW2
                //object? result2 = plc.Read(S7.Net.DataType.DataBlock, 1, 2, VarType.Int, 1);
                //txtDBW2.Text = result2.ToString();
                //// PLC real->c# float DB1.DBD4
                //object? result3 = plc.Read(S7.Net.DataType.DataBlock, 1, 4, VarType.Real, 1);
                //txtDBD4.Text = result3.ToString();
                //// 读取字符串
                //string result4 = ReadPlcString(plc, 1, 8);  // DB1.DBX8.0 开始的 STRING[10]
                //txtStr8.Text = result4;
                //// 错误的方式:
                ////不要用 Read(... VarType.String)	它只适用于没有长度前缀的纯字符数组
                ////plc.Read(DataType.DataBlock, 1, 8, VarType.String, 1);
                ///**
                // * VarType.String 并不是用来读取 PLC 中的 STRING 类型。
                //这个方法是用于写入或读取 C# 字符串到 PLC 的连续字符区域（不带长度信息），但不适合读取带有长度字段的 STRING[n] 类型。
                //length=1 只读了 1 字节，不足以构成一个完整的 STRING。
                // * 
                // */


                // 读取db1从第0个字节开始读20个byte
                byte[] resultBytes = plc.ReadBytes(S7.Net.DataType.DataBlock, 1, 0, 20);

                //Word DB1.DBW0
                ushort us = BitConverter.ToUInt16([resultBytes[1], resultBytes[0]]);
                txtDBW0.Text = us.ToString();
                // PLC Int-->c# short DB1.DBW2
                short s = BitConverter.ToInt16([resultBytes[3], resultBytes[2]]);
                txtDBW2.Text = s.ToString();
                // PLC real->c# float DB1.DBD4
                float f = BitConverter.ToSingle([resultBytes[7], resultBytes[6], resultBytes[5], resultBytes[4]]);
                txtDBD4.Text = f.ToString();

                //resultBytes[10] 字符串的总长度,resultBytes[9]:表示字符串的实际长度
                byte[] strBytes = resultBytes.ToList().GetRange(10, resultBytes[9]).ToArray();
                string str = Encoding.Default.GetString(strBytes);
                txtStr8.Text = str.ToString();

            }

        }
        #endregion


        private void btnTest_Click(object sender, RoutedEventArgs e)
        {
            
        }

        #region 写Q区
        /// <summary>
        /// 写Q0.0
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cbQ00_Click(object sender, RoutedEventArgs e)
        {
            if (plc != null && plc.IsConnected)
            {
                //S7.Net.DataType.Output :代表Q区
                // 参数2 DB区编号设置为0，因为不读db区
                // 参数3 写的字节地址
                // 参数4 写的位地址 
                // 参数5 写的值
                //plc.WriteBit(S7.Net.DataType.Output,0,0,0,Q00);
                // 还可以这样写
                plc.Write("Q0.0", Q00);

                // 比如给DB1的第0个字节写一个字节
                //plc.Write("DB1.DBB0",0x00);
            }
        }

        /// <summary>
        /// 写Q0.1
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>

        private void cbQ01_Click(object sender, RoutedEventArgs e)
        {
            if (plc != null && plc.IsConnected)
            {
                //S7.Net.DataType.Output :代表Q区
                // 参数2 DB区编号设置为0，因为不读db区
                // 参数3 写的字节地址
                // 参数4 写的位地址 
                // 参数5 写的值
                plc.WriteBit(S7.Net.DataType.Output, 0, 0, 1, Q01);
                // 还可以这样写
                //plc.Write("Q0.1", Q01);

                // 比如给DB1的第0个字节写一个字节
                //plc.Write("DB1.DBB0",0x00);
            }
        }


        /// <summary>
        /// 写Q0.2
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cbQ02_Click(object sender, RoutedEventArgs e)
        {
            if (plc != null && plc.IsConnected)
            {
                //S7.Net.DataType.Output :代表Q区
                // 参数2 DB区编号设置为0，因为不读db区
                // 参数3 写的字节地址
                // 参数4 写的位地址 
                // 参数5 写的值
                plc.WriteBit(S7.Net.DataType.Output, 0, 0, 2, Q02);
                // 还可以这样写
                //plc.Write("Q0.2", Q02);

                // 比如给DB1的第0个字节写一个字节
                //plc.Write("DB1.DBB0",0x00);
            }
        }

        /// <summary>
        /// 写Q0.3
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cbQ03_Click(object sender, RoutedEventArgs e)
        {
            if (plc != null && plc.IsConnected)
            {
                //S7.Net.DataType.Output :代表Q区
                // 参数2 DB区编号设置为0，因为不读db区
                // 参数3 写的字节地址
                // 参数4 写的位地址 
                // 参数5 写的值
                plc.WriteBit(S7.Net.DataType.Output, 0, 0, 3, Q03);
                // 还可以这样写
                //plc.Write("Q0.3", Q03);

                // 比如给DB1的第0个字节写一个字节
                //plc.Write("DB1.DBB0",0x00);
            }
        }

        /// <summary>
        /// 写Q0.4
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cbQ04_Click(object sender, RoutedEventArgs e)
        {
            if (plc != null && plc.IsConnected)
            {
                //S7.Net.DataType.Output :代表Q区
                // 参数2 DB区编号设置为0，因为不读db区
                // 参数3 写的字节地址
                // 参数4 写的位地址 
                // 参数5 写的值
                plc.WriteBit(S7.Net.DataType.Output, 0, 0, 4, Q04);
                // 还可以这样写
                //plc.Write("Q0.4", Q04);

                // 比如给DB1的第0个字节写一个字节
                //plc.Write("DB1.DBB0",0x00);
            }
        }

        /// <summary>
        /// 写Q0.5
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cbQ05_Click(object sender, RoutedEventArgs e)
        {
            if (plc != null && plc.IsConnected)
            {
                //S7.Net.DataType.Output :代表Q区
                // 参数2 DB区编号设置为0，因为不读db区
                // 参数3 写的字节地址
                // 参数4 写的位地址 
                // 参数5 写的值
                plc.WriteBit(S7.Net.DataType.Output, 0, 0, 5, Q05);
                // 还可以这样写
                //plc.Write("Q0.5", Q05);

                // 比如给DB1的第0个字节写一个字节
                //plc.Write("DB1.DBB0",0x00);
            }
        }

        /// <summary>
        /// 写Q0.6
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cbQ06_Click(object sender, RoutedEventArgs e)
        {
            if (plc != null && plc.IsConnected)
            {
                //S7.Net.DataType.Output :代表Q区
                // 参数2 DB区编号设置为0，因为不读db区
                // 参数3 写的字节地址
                // 参数4 写的位地址 
                // 参数5 写的值
                plc.WriteBit(S7.Net.DataType.Output, 0, 0, 6, Q06);
                // 还可以这样写
                //plc.Write("Q0.6, Q06);

                // 比如给DB1的第0个字节写一个字节
                //plc.Write("DB1.DBB0",0x00);
            }
        }

        /// <summary>
        /// 写Q0.7
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cbQ07_Click(object sender, RoutedEventArgs e)
        {
            if (plc != null && plc.IsConnected)
            {
                //S7.Net.DataType.Output :代表Q区
                // 参数2 DB区编号设置为0，因为不读db区
                // 参数3 写的字节地址
                // 参数4 写的位地址 
                // 参数5 写的值
                plc.WriteBit(S7.Net.DataType.Output, 0, 0, 7, Q07);
                // 还可以这样写
                //plc.Write("Q0.7, Q07);

                // 比如给DB1的第0个字节写一个字节
                //plc.Write("DB1.DBB0",0x00);
            }
        }
        #endregion


        #region 写DB区
        private void txtDBW0_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (txtDBW0.Text == "")
            {
                return;
            }

            if (plc != null && plc.IsConnected)
            {
                ushort us = ushort.Parse(txtDBW0.Text);
                byte[] usBytes = BitConverter.GetBytes(us).Reverse().ToArray();
                // 写字节的方式
                plc.WriteBytes(S7.Net.DataType.DataBlock, 1, 0, usBytes);
                // 直接写一个ushort进去,会根据你传进值的数据类型转换为字节数组
                //plc.Write(S7.Net.DataType.DataBlock, 1, 0, us);
            }
        }

        private void txtDBW2_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (txtDBW2.Text == "")
            {
                return;
            }

            if (plc != null && plc.IsConnected)
            {
                short s = short.Parse(txtDBW2.Text);
                byte[] sBytes = BitConverter.GetBytes(s).Reverse().ToArray();
                // 写字节的方式
                //plc.WriteBytes(S7.Net.DataType.DataBlock, 1, 2, sBytes);
                // 直接写一个short进去,会根据你传进值的数据类型转换为字节数组
                plc.Write(S7.Net.DataType.DataBlock, 1, 2, s);
            }
        }

        private void txtDBD4_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (txtDBD4.Text == "")
            {
                return;
            }

            if (plc != null && plc.IsConnected)
            {
                float f = float.Parse(txtDBD4.Text);
                byte[] fBytes = BitConverter.GetBytes(f).Reverse().ToArray();
                // 写字节的方式
                //plc.WriteBytes(S7.Net.DataType.DataBlock, 1, 4, fBytes);
                // 直接写一个float进去,会根据你传进值的数据类型转换为字节数组
                plc.Write(S7.Net.DataType.DataBlock, 1, 4, f);
            }
        }

        private void txtStr8_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (txtStr8.Text.Length > 10)
            {
                MessageBox.Show("字符串长度不能超过10");
                return;
            }

            if (plc != null && plc.IsConnected)
            {
                byte[] strBytes = Encoding.Default.GetBytes(txtStr8.Text);
                //totalBytes[0] 字符串的总长度,totalBytes[1]:表示字符串的实际长度
                List<byte> totalBytes = [0x0a, (byte)strBytes.Length];
                totalBytes.AddRange(strBytes);

                plc.WriteBytes(S7.Net.DataType.DataBlock, 1, 8, totalBytes.ToArray());
            }
        }

        #endregion


    }
}
